package com.lingjtx.oss.common;

import com.lingjtx.common.core.algorithm.Base64Util;
import com.lingjtx.common.core.constant.Const;
import com.lingjtx.common.core.constant.OSSFolderName;
import com.lingjtx.common.core.model.api.OssUploadResponseItem;
import com.lingjtx.common.core.util.StringUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class ReactiveMinioService {

    private static final Logger log = LoggerFactory.getLogger(ReactiveMinioService.class);

    private static final String PRE_SIGNED_URL = Const.SERVER_URL + "/oss/image/";

    @Value("${minio.bucket}")
    private String bucketName;

    @Value("${minio.endpoint}")
    private String endpoint;

    private final MinioClient minioClient;

    public ReactiveMinioService(MinioClient minioClient) {
        this.minioClient = minioClient;
    }

    /**
     * 支持 FilePart 文件上传（非阻塞）
     */
    public Mono<OssUploadResponseItem> uploadFile(Integer folderType, FilePart filePart) {
        String objectName = OSSFolderName.codeOf(folderType).getFolder()
                + StringUtil.getUuid()
                + Optional.of(filePart.filename())
                .map(name -> name.substring(name.lastIndexOf(".")))
                .orElse("");

        return Mono.usingWhen(
                Mono.fromCallable(() -> Files.createTempFile("minio-", "-" + filePart.filename()))
                        .subscribeOn(Schedulers.boundedElastic()),
                tempFile -> filePart.transferTo(tempFile)
                        .then(Mono.fromCallable(() -> Files.newInputStream(tempFile)))
                        .flatMap(inputStream -> uploadFile(objectName, inputStream, isImage(filePart))),
                tempFile -> Mono.fromRunnable(() -> {
                    try {
                        Files.deleteIfExists(tempFile);
                    } catch (IOException e) {
                        log.warn("临时文件删除失败: {}", e.getMessage());
                    }
                }).subscribeOn(Schedulers.boundedElastic())
        );
    }

    /**
     * 上传 InputStream
     */
    public Mono<OssUploadResponseItem> uploadFile(String objectName, InputStream inputStream, boolean isImg) {
        return Mono.fromCallable(() -> {
                    try (InputStream in = inputStream) {
                        long size = in.available();
                        long partSize = size > 10 * 1024 * 1024 ? 10 * 1024 * 1024 : -1;

                        String contentType = Optional.ofNullable(URLConnection.guessContentTypeFromName(objectName))
                                .orElse("application/octet-stream");

                        minioClient.putObject(PutObjectArgs.builder()
                                .bucket(bucketName)
                                .object(objectName)
                                .stream(in, size, partSize)
                                .contentType(contentType)
                                .build());

                        String url = isImg ? PRE_SIGNED_URL + Base64Util.encode(objectName) : endpoint + "/" + objectName;
                        return new OssUploadResponseItem(objectName, url);
                    }
                })
                .subscribeOn(Schedulers.boundedElastic())
                .onErrorResume(e -> {
                    log.error("MinIO 上传失败: {}", e.getMessage(), e);
                    return Mono.error(new RuntimeException("上传失败", e));
                });
    }

    public Mono<String> getPreSignedUrl(String objectName) {
        return Mono.fromCallable(() ->
                        minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                                .bucket(bucketName)
                                .object(objectName)
                                .method(Method.GET)
                                .expiry(1, TimeUnit.HOURS)
                                .build()))
                .subscribeOn(Schedulers.boundedElastic())
                .onErrorResume(e -> {
                    log.error("获取预签名链接失败: {}", e.getMessage(), e);
                    return Mono.empty();
                });
    }

    public Mono<ResponseEntity<Flux<DataBuffer>>> downloadAsFlux(String objectName) {
        return Mono.fromCallable(() -> minioClient.getObject(
                        GetObjectArgs.builder()
                                .bucket(bucketName)
                                .object(objectName)
                                .build()))
                .subscribeOn(Schedulers.boundedElastic())
                .flatMap(inputStream -> {
                    Flux<DataBuffer> dataBufferFlux = DataBufferUtils.readInputStream(
                                    () -> inputStream,
                                    new DefaultDataBufferFactory(),
                                    4096)
                            .doOnTerminate(() -> {
                                try {
                                    inputStream.close();
                                } catch (IOException e) {
                                    log.warn("Failed to close stream", e);
                                }
                            });

                    // 处理文件名（防止中文乱码）
                    String encodedFileName = URLEncoder.encode(objectName, StandardCharsets.UTF_8);

                    return Mono.just(ResponseEntity.ok()
                            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName)
                            .contentType(MediaType.APPLICATION_OCTET_STREAM)
                            .body(dataBufferFlux));
                })
                .onErrorResume(e -> {
                    log.error("下载文件失败: {}", e.getMessage(), e);
                    return Mono.just(ResponseEntity.notFound().build());
                });
    }

    public Mono<Void> delete(List<String> objectNames) {
        List<DeleteObject> deleteObjects = objectNames.stream()
                .map(DeleteObject::new)
                .collect(Collectors.toList());

        return Mono.fromRunnable(() -> {
            minioClient.removeObjects(RemoveObjectsArgs.builder()
                    .bucket(bucketName)
                    .objects(deleteObjects)
                    .build());
        }).subscribeOn(Schedulers.boundedElastic()).then();
    }

    public boolean isImage(FilePart filePart) {
        MediaType contentType = filePart.headers().getContentType();
        return contentType != null && contentType.getType().equalsIgnoreCase("image");
    }

    public boolean isImage(String type) {
        return ".jpg".equals(type) || ".jpeg".equals(type) || ".png".equals(type)
                || ".gif".equals(type) || ".bmp".equals(type) || ".webp".equals(type);
    }
}
